// Package menu_service_v1
// @Author zhongxc
// @Date 2024/7/9 15:22:00
// @Desc
package menu_service_v1

import (
	"context"
	"fmt"
	"github.com/jinzhu/copier"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"time"
	"vben-admin/pkg/dao"
	"vben-admin/pkg/model"
	"vben-admin/pkg/repository"
	"vben-admin/pkg/svc"
	"vben-common/datatypes"
	menuService "vben-grpc-common/vben-admin/types/menu.service.v1"
)

type MenuService struct {
	menuService.UnimplementedMenuServiceServer
	dao    repository.MenuRepo
	svcCtx *svc.ServiceContext
}

func NewMenuService(svcCtx *svc.ServiceContext) *MenuService {
	return &MenuService{
		svcCtx: svcCtx,
		dao:    dao.NewMenuDao(svcCtx.Db),
	}
}

func (m *MenuService) SelectMenuTree(ctx context.Context, in *emptypb.Empty) (*menuService.MenuListResponse, error) {

	menuList, err := m.dao.SelectMenu(ctx)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("菜单查询失败: %v", err))
		return nil, status.Error(codes.FailedPrecondition, "菜单查询失败")
	}

	menuTree := m.menuTree(menuList, 0)
	return &menuService.MenuListResponse{
		Data: menuTree,
	}, nil
}

func (m *MenuService) DeleteMenu(ctx context.Context, in *menuService.DeleteMenuRequest) (*emptypb.Empty, error) {
	if len(in.Ids) == 0 {
		return nil, status.Error(codes.InvalidArgument, "菜单id不能为空")
	}

	err := m.dao.DeleteByIds(ctx, in.Ids)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("菜单删除失败:%v", err))
		return nil, status.Error(codes.FailedPrecondition, "菜单删除失败")
	}

	return &emptypb.Empty{}, nil
}

func (m *MenuService) UpdateMenu(ctx context.Context, in *menuService.UpdateMenuRequest) (*emptypb.Empty, error) {
	if in.Id == 0 {
		return nil, status.Error(codes.FailedPrecondition, "id不能为空")
	}
	err := m.dao.UpdateById(ctx, in.Id, map[string]interface{}{
		"name":       in.Name,
		"url":        in.Url,
		"perms":      in.Perms,
		"type":       in.Type,
		"icon":       in.Icon,
		"order_no":   in.OrderNo,
		"parent_id":  in.ParentId,
		"updated_by": "admin",
		"updated_at": datatypes.Time(time.Now()),
	})
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("菜单更新失败: %v", err))
		return nil, status.Error(codes.FailedPrecondition, "菜单更新失败")
	}
	return &emptypb.Empty{}, nil
}

func (m *MenuService) CreateMenu(ctx context.Context, req *menuService.CreateMenuRequest) (*emptypb.Empty, error) {

	if req.Type == int32(model.MenuTypeButton) {
		return nil, status.Error(codes.FailedPrecondition, "按钮类型菜单不能添加子菜单")
	}

	// 检查菜单名字是否已存在
	if req.Type == int32(model.MenuTypeMenu) {
		menu, err := m.dao.GetMenuByName(ctx, req.Name)
		if err != nil {
			m.svcCtx.ZapLog.Error(fmt.Sprintf("获取菜单失败: %v", err))
			return nil, status.Error(codes.FailedPrecondition, "获取菜单失败")
		}
		if menu != nil {
			return nil, status.Error(codes.FailedPrecondition, "菜单名已存在")
		}
	}

	data := &model.MenuModel{
		ParentId:  req.ParentId,
		Name:      req.Name,
		Url:       req.Url,
		Perms:     req.Perms,
		Type:      model.MenuType(req.Type),
		Icon:      req.Icon,
		OrderNo:   req.OrderNo,
		Status:    1,
		CreatedBy: req.CreatedBy,
		CreatedAt: datatypes.Time(time.Now()),
	}
	err := m.dao.CreateMenu(ctx, data)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("创建菜单失败: %v", err))
		return nil, status.Error(codes.FailedPrecondition, "创建菜单失败")
	}
	return &emptypb.Empty{}, nil
}

func (m *MenuService) GetMenuById(ctx context.Context, in *menuService.GetMenuRequest) (*menuService.MenuResponse, error) {
	menu, err := m.dao.GetMenuById(ctx, in.Id)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("获取菜单失败: %v", err))
		return nil, status.Error(codes.FailedPrecondition, "获取菜单失败")
	}
	return &menuService.MenuResponse{
		Id:        menu.Id,
		Name:      menu.Name,
		Url:       menu.Url,
		Perms:     menu.Perms,
		Type:      int32(menu.Type),
		Icon:      menu.Icon,
		OrderNo:   menu.OrderNo,
		ParentId:  menu.ParentId,
		CreatedBy: menu.CreatedBy,
	}, nil
}

func (m *MenuService) menuTree(list []*model.MenuModel, parentId int64) []*menuService.MenuItem {
	if list == nil {
		return nil
	}

	var menuItems = make([]*menuService.MenuItem, 0)
	for _, menu := range list {
		if menu.ParentId == parentId {
			menuItem := &menuService.MenuItem{
				Id:        menu.Id,
				ParentId:  menu.ParentId,
				Name:      menu.Name,
				Url:       menu.Url,
				Perms:     menu.Perms,
				Type:      int32(menu.Type),
				Icon:      menu.Icon,
				OrderNo:   menu.OrderNo,
				Status:    int32(menu.Status),
				CreatedBy: menu.CreatedBy,
				Children:  make([]*menuService.MenuItem, 0),
			}
			menuItem.Children = m.buildMenuTreeChild(list, menu.Id)
			menuItems = append(menuItems, menuItem)
		}
	}
	return menuItems
}

func (m *MenuService) buildMenuTreeChild(list []*model.MenuModel, id int64) []*menuService.MenuItem {
	var menuItems = make([]*menuService.MenuItem, 0)
	for _, menu := range list {
		if menu.ParentId == id {
			menuItem := &menuService.MenuItem{
				Id:        menu.Id,
				ParentId:  menu.ParentId,
				Name:      menu.Name,
				Url:       menu.Url,
				Perms:     menu.Perms,
				Type:      int32(menu.Type),
				Icon:      menu.Icon,
				OrderNo:   menu.OrderNo,
				Status:    int32(menu.Status),
				CreatedBy: menu.CreatedBy,
				CreatedAt: menu.CreatedAt.String(),
				Children:  make([]*menuService.MenuItem, 0),
			}
			menuItem.Children = m.buildMenuTreeChild(list, menu.Id)
			menuItems = append(menuItems, menuItem)
		}
	}
	return menuItems
}

func (m *MenuService) GetMenuList(ctx context.Context, in *menuService.MenuListRequest) (*menuService.MenuListResponse, error) {
	menus, err := m.dao.GetMenuList(ctx, in.Name, in.Status)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("获取菜单列表失败: %v", err))
		return nil, status.Error(codes.FailedPrecondition, "获取菜单列表失败")
	}

	menuTreeList := m.menuTree(menus, 0)

	return &menuService.MenuListResponse{
		Data: menuTreeList,
	}, nil
}

// SelectMenuByUserId
//
//	@Description: 根据用户id获取菜单列表
//	@receiver m
//	@param ctx
//	@param in
//	@return *menuService.MenuListResponse
//	@return error
func (m *MenuService) SelectMenuByUserId(ctx context.Context, in *menuService.SelectMenuByUserIdRequest) (*menuService.MenuListResponse, error) {

	if in.UserId == 0 {
		return nil, status.Error(codes.InvalidArgument, "用户id不能为空")
	}

	// 查询出角色关联的所有菜单的id
	menuIdList, err := m.dao.SelectMenuByUserId(ctx, in.UserId)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("获取菜单列表失败: %v", err))
		return nil, status.Error(codes.Internal, "获取菜单列表失败")
	}

	// 如果为空，直接返回
	if len(menuIdList) == 0 {
		return &menuService.MenuListResponse{}, nil
	}

	menuList, err := m.getAllMenuList(ctx, menuIdList)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("获取菜单列表失败: %v", err))
		return nil, status.Error(codes.Internal, "获取菜单列表失败")
	}

	return &menuService.MenuListResponse{
		Data: menuList,
	}, nil
}

func (m *MenuService) getAllMenuList(ctx context.Context, menuIdList []int64) ([]*menuService.MenuItem, error) {
	// 查询出所有的一级（根）菜单
	rootMenuList, err := m.getSubMenuChildren(ctx, 0, menuIdList)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("查询出所有的一级（根）菜单: %v", err))
		return nil, err
	}

	// 递归获取子菜单
	subMenuList := m.getSubMenuList(ctx, rootMenuList, menuIdList)

	return subMenuList, nil
}

func (m *MenuService) getSubMenuList(ctx context.Context, menuList []*model.MenuModel, menuIdList []int64) []*menuService.MenuItem {
	var subMenuList []*menuService.MenuItem
	for _, menu := range menuList {
		var menuItem menuService.MenuItem
		if menu.Type == 0 || menu.Type == 1 {
			// 获取当前菜单下的子菜单
			subMenu, _ := m.getSubMenuChildren(ctx, menu.Id, menuIdList)
			_ = copier.Copy(&menuItem, menu)
			list := m.getSubMenuList(ctx, subMenu, menuIdList)
			menuItem.Children = list
		}
		subMenuList = append(subMenuList, &menuItem)
	}
	return subMenuList
}

func (m *MenuService) getSubMenuChildren(ctx context.Context, id int64, menuIdList []int64) ([]*model.MenuModel, error) {
	menuList, err := m.dao.FindByParentId(ctx, id)
	if err != nil {
		m.svcCtx.ZapLog.Error(fmt.Sprintf("查询出所有的一级（根）菜单: %v", err))
		return nil, err
	}

	if len(menuIdList) == 0 {
		return nil, nil
	}

	var userMenuList []*model.MenuModel
	for _, menu := range menuList {
		for _, id := range menuIdList {
			if menu.Id == id {
				userMenuList = append(userMenuList, menu)
			}
		}
	}
	return userMenuList, nil
}
